/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkAssignStmt, ArkField, ArkFile, ArkInstanceFieldRef, ArkMethod, ArkNewExpr, ArkStaticFieldRef, ClassSignature, ClassType, DEFAULT_ARK_CLASS_NAME, ImportInfo, Local, Stmt, Value } from 'arkanalyzer/lib';
import { ArkClass } from 'arkanalyzer/lib/core/model/ArkClass';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { Rule, Defects, MethodMatcher, MatcherTypes, MatcherCallback, CheckerUtils, CheckerStorage, Scope } from '../../Index';
import { IssueReport } from '../../model/Defects';
import { VarInfo } from '../../model/VarInfo';
import { NumberUtils } from '../../utils/checker/NumberUtils';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoHighLoadedFrameRateRangeCheck');

// 类型定义
interface IssueInfo {
    severity: number;
    filePath: string;
    lineNum: number;
    startColumn: number;
    endColumn: number;
}

interface FrameRateValues {
    expected?: number;
    min?: number;
    max?: number;
}

// 常量定义
const HIGH_FRAME_RATE_THRESHOLD = 120;
const EXPECTED_FIELD_COUNT = 3;
const VALID_FIELD_NAMES = ['expected', 'min', 'max'] as const;
const SPECIAL_FIELD_NAME = 'expectedFrameRateRange';
const MAX_RECURSION_DEPTH = 10;

const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: 'docs/no-high-loaded-frame-rate-range.md',
    description: `Avoid setting the expected, min, and max values of ExpectedFrameRateRange all to ${HIGH_FRAME_RATE_THRESHOLD} or higher.`
};
const setExpectedFrameRateRangeSignature: string[] = [
    '@ohosSdk/api/@ohos.graphics.displaySync.d.ts: displaySync.DisplaySync.setExpectedFrameRateRange(ExpectedFrameRateRange)',
    '@ohosSdk/api/@ohos.graphics.displaySync.d.ts: displaySync.DisplaySync.setExpectedFrameRateRange(@ohosSdk/component/common.d.ts: ExpectedFrameRateRange)'
];
const animateToSignature = `@ohosSdk/component/common.d.ts: ${DEFAULT_ARK_CLASS_NAME}.animateTo(@ohosSdk/component/common.d.ts: AnimateParam, @ohosSdk/component/common.d.ts: ${DEFAULT_ARK_CLASS_NAME}.%AM0())`;
const setFrameRateRangeSignature: string[] = [
    '@ohosSdk/api/@ohos.arkui.UIContext.d.ts: DynamicSyncScene.setFrameRateRange(ExpectedFrameRateRange)',
    '@ohosSdk/api/@ohos.arkui.UIContext.d.ts: DynamicSyncScene.setFrameRateRange(@ohosSdk/component/common.d.ts: ExpectedFrameRateRange)'
];

export class NoHighLoadedFrameRateRangeCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD
    };

    public registerMatchers(): MatcherCallback[] {
        const matchMethodCb: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check
        };
        return [matchMethodCb];
    }

    private arkFile: ArkFile;

    public check = (arkMethod: ArkMethod): void => {
        this.arkFile = arkMethod.getDeclaringArkFile();
        const originPath = this.arkFile.getName();
        if (!this.isEtsFile(originPath)) {
            return;
        }
        const stmts = arkMethod.getBody()?.getCfg().getStmts();
        if (!stmts) {
            return;
        }

        for (const stmt of stmts) {
            this.processStatement(stmt, arkMethod);
        }
    };

    private processStatement(stmt: Stmt, arkMethod: ArkMethod): void {
        const invoke = CheckerUtils.getInvokeExprFromStmt(stmt);
        if (!invoke) {
            return;
        }

        if (!this.isTargetMethodSignature(invoke.getMethodSignature().toString())) {
            return;
        }

        // 检查第一个参数（ExpectedFrameRateRange 参数）
        const arg = invoke.getArg(0);
        if (!(arg instanceof Local)) {
            return;
        }

        const fieldValues = this.getFieldNum(stmt, arg, arg.getName());
        if (this.isHighLoadedFrameRate(fieldValues)) {
            const methodName = invoke.getMethodSignature().getMethodSubSignature().getMethodName();
            this.reportIssue(arkMethod.getDeclaringArkFile(), stmt, methodName);
        }
    }

    /**
     * 检查是否为高负载帧率配置
     * 当 expected、min、max 三个值都 >= 120 时认为是高负载
     */
    private isHighLoadedFrameRate(fieldValues: number[]): boolean {
        if (fieldValues.length !== EXPECTED_FIELD_COUNT) {
            return false;
        }

        // 检查是否有无效值
        if (fieldValues.some(value => !Number.isFinite(value) || value < 0)) {
            return false;
        }

        // 所有值都必须 >= 120
        return fieldValues.every(value => value >= HIGH_FRAME_RATE_THRESHOLD);
    }

    private isTargetMethodSignature(methodSignature: string): boolean {
        return setExpectedFrameRateRangeSignature.includes(methodSignature) ||
            methodSignature === animateToSignature ||
            setFrameRateRangeSignature.includes(methodSignature);
    }

    private getFieldNum(stmt: Stmt, arg: Local, name: string, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            logger.warn(`Maximum recursion depth reached for ${name}`);
            return [];
        }

        try {
            const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile();
            const declaringStmt = arg.getDeclaringStmt();

            // 场景一：局部变量
            if (declaringStmt) {
                const fieldValue = this.traversalLocals(declaringStmt, depth);
                if (this.isValidFieldValueArray(fieldValue)) {
                    return fieldValue;
                }
            }

            // 场景二：全局变量
            const globalValues = this.traversalDefaultClass(arkFile, name, depth);
            if (this.isValidFieldValueArray(globalValues)) {
                return globalValues;
            }

            // 场景三：import模块
            const importValues = this.traversalImport(arkFile, name, depth);
            return this.isValidFieldValueArray(importValues) ? importValues : [];
        } catch (error) {
            logger.error(`Error processing field ${name}: ${error}`);
            return [];
        }
    }

    /**
     * 验证字段值数组是否有效
     */
    private isValidFieldValueArray(values: number[]): boolean {
        return values.length > 0 && values.every(value => Number.isFinite(value) && value >= 0);
    }

    private traversalImport(arkFile: ArkFile, name: string, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const imports = arkFile.getImportInfos();
        let paramsValues: number[] = [];

        for (const subImport of imports) {
            if (subImport.getImportClauseName() !== name) {
                continue;
            }

            const importArkFile = subImport.getLazyExportInfo()?.getDeclaringArkFile();
            if (!importArkFile) {
                return this.traversaModuleFile(arkFile, subImport, depth);
            }

            paramsValues = this.traversalDefaultClass(importArkFile, name, depth);
            if (paramsValues.length > 0) {
                return paramsValues;
            }

            paramsValues = this.processExportInfos(importArkFile, name, depth);
            if (paramsValues.length > 0) {
                return paramsValues;
            }

            return this.traversaModuleFile(arkFile, subImport, depth);
        }
        return paramsValues;
    }

    private processExportInfos(importArkFile: ArkFile, name: string, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const exportInfos = importArkFile.getExportInfos();

        for (const exportInfo of exportInfos) {
            if (exportInfo.getExportClauseName() !== name) {
                continue;
            }

            const arkExport = exportInfo.getArkExport();
            if (!(arkExport instanceof Local)) {
                continue;
            }

            const result = this.processExportLocal(arkExport, depth);
            if (result.length > 0) {
                return result;
            }
        }
        return [];
    }

    private processExportLocal(arkExport: Local, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const paramsStmt = arkExport.getDeclaringStmt();
        if (!(paramsStmt instanceof ArkAssignStmt)) {
            return [];
        }

        const paramsRightOp = paramsStmt.getRightOp();
        if (!(paramsRightOp instanceof Local)) {
            return [];
        }

        const paramsType = paramsRightOp.getType();
        if (!(paramsType instanceof ClassType)) {
            return [];
        }

        const paramsArkFile = paramsStmt.getCfg().getDeclaringMethod().getDeclaringArkFile();
        const paramsClassSignature = paramsType.getClassSignature();
        const paramsFields = paramsArkFile?.getClass(paramsClassSignature)?.getFields();

        if (!paramsFields) {
            return [];
        }

        return this.getReasonableFieldValues(paramsFields, depth);
    }

    private traversaModuleFile(arkFile: ArkFile, subImport: ImportInfo, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const from = subImport.getFrom() ?? '';
        const moduleSence = arkFile?.getScene().getModuleScene(from);
        if (!moduleSence) {
            return [];
        }
        const moduleFiles = moduleSence.getModuleFilesMap();
        return this.traverseModuleFileMap(moduleFiles, subImport, depth);
    }

    private traverseModuleFileMap(moduleFiles: Map<string, ArkFile>, subImport: ImportInfo, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        for (const moduleFile of moduleFiles.values()) {
            const defClassValues = this.traversalDefaultClass(moduleFile, subImport.getImportClauseName(), depth);
            if (defClassValues.length > 0) {
                return defClassValues;
            }

            const fieldValues = this.traverseFields(moduleFile, depth);
            if (fieldValues.length > 0) {
                return fieldValues;
            }
        }
        return [];
    }

    private traverseFields(moduleFile: ArkFile, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        for (const arkClass of moduleFile.getClasses()) {
            const result = this.processClassFields(arkClass, depth);
            if (result.length > 0) {
                return result;
            }
        }
        return [];
    }

    private processClassFields(arkClass: ArkClass, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        for (const field of arkClass.getFields()) {
            const initializer = field.getInitializer()[0];
            const initValue = this.traversalLocals(initializer, depth);
            if (initValue.length > 0) {
                return initValue;
            }
        }
        return [];
    }

    private traversalLocals(declaringStmt: Stmt, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        if (!(declaringStmt instanceof ArkAssignStmt)) {
            return [];
        }

        const method = declaringStmt.getCfg().getDeclaringMethod();
        const rightOp = declaringStmt.getRightOp();

        if (rightOp instanceof Local || rightOp instanceof ArkNewExpr) {
            return this.processLocalOrNewExpr(rightOp, method, depth);
        }

        if (rightOp instanceof ArkInstanceFieldRef) {
            return this.processInstanceFieldRef(rightOp, method, depth);
        }

        if (rightOp instanceof ArkStaticFieldRef) {
            return this.processStaticFieldRef(rightOp, method, depth);
        }

        return [];
    }

    private processLocalOrNewExpr(rightOp: Local | ArkNewExpr, method: ArkMethod, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const type = rightOp.getType();
        if (!(type instanceof ClassType)) {
            return [];
        }

        const arkClass = method.getDeclaringArkFile().getScene().getClass(type.getClassSignature());
        const fields = arkClass?.getFields();
        if (!fields) {
            return [];
        }

        return this.getReasonableFieldValues(fields, depth);
    }

    private processInstanceFieldRef(rightOp: ArkInstanceFieldRef, method: ArkMethod, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const field = method.getDeclaringArkClass().getField(rightOp.getFieldSignature());
        if (field) {
            const initializer = field.getInitializer()[0];
            return this.traversalLocals(initializer, depth + 1);
        }

        return this.traversalImport(method.getDeclaringArkFile(), rightOp.getBase().getName(), depth + 1);
    }

    private processStaticFieldRef(rightOp: ArkStaticFieldRef, method: ArkMethod, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const fieldSignature = rightOp.getFieldSignature();
        const declareSignature = fieldSignature.getDeclaringSignature();
        if (!(declareSignature instanceof ClassSignature)) {
            return [];
        }

        const declareClass = method.getDeclaringArkFile().getScene().getClass(declareSignature);
        const field = declareClass?.getStaticFieldWithName(rightOp.getFieldName());
        if (!field) {
            return [];
        }

        const initializer = field.getInitializer()[0];
        return this.traversalLocals(initializer, depth + 1);
    }

    private traversalDefaultClass(arkFile: ArkFile, name: string, depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const defaultClass = arkFile.getDefaultClass();
        const method = defaultClass.getMethods()[0];
        const locals = method.getBody()?.getLocals();
        if (!locals) {
            return [];
        }

        for (const [key, value] of locals) {
            if (!(value instanceof Local) || key !== name) {
                continue;
            }

            const declaringStmt = value.getDeclaringStmt();
            if (!declaringStmt) {
                return [];
            }

            return this.traversalLocals(declaringStmt, depth + 1);
        }
        return [];
    }

    private getReasonableFieldValues(fields: ArkField[], depth: number = 0): number[] {
        if (depth > MAX_RECURSION_DEPTH) {
            return [];
        }

        const fieldValues: FrameRateValues = {};
        const resultArray: number[] = [];

        for (const field of fields) {
            // 特殊处理 expectedFrameRateRange 字段
            if (field.getName() === SPECIAL_FIELD_NAME) {
                return this.traversalLocals(field.getInitializer()[0], depth + 1);
            }

            // 只处理 expected、min、max 字段
            if (!this.isValidFieldName(field.getName())) {
                continue;
            }

            const value = this.processFieldValue(field, depth);
            if (value !== null) {
                const fieldName = field.getName() as keyof FrameRateValues;
                fieldValues[fieldName] = value;

                // 只有当值 >= 120 时才添加到结果数组
                if (value >= HIGH_FRAME_RATE_THRESHOLD) {
                    resultArray.push(value);
                }
            }
        }

        // 确保我们有完整的三个字段值且都 >= 120
        const hasAllHighValues = VALID_FIELD_NAMES.every(fieldName =>
            fieldValues[fieldName] !== undefined &&
            fieldValues[fieldName]! >= HIGH_FRAME_RATE_THRESHOLD
        );

        return hasAllHighValues ? resultArray : [];
    }

    private isValidFieldName(fieldName: string): fieldName is typeof VALID_FIELD_NAMES[number] {
        return (VALID_FIELD_NAMES as readonly string[]).includes(fieldName);
    }

    private processFieldValue(field: ArkField, depth: number = 0): number | null {
        if (depth > MAX_RECURSION_DEPTH) {
            return null;
        }

        const initializer = field.getInitializer()[0];
        if (!(initializer instanceof ArkAssignStmt)) {
            return null;
        }

        const arkFile = field.getDeclaringArkClass().getDeclaringArkFile();
        const rightOp = initializer.getRightOp();
        const scope = CheckerStorage.getInstance().getScope(arkFile.getFilePath());

        if (!scope) {
            return null;
        }

        return this.processScope(scope, initializer, arkFile, rightOp, depth);
    }

    private processScope(scope: Scope, initializer: ArkAssignStmt, arkFile: ArkFile, rightOp: Value, depth: number = 0): number | null {
        if (depth > MAX_RECURSION_DEPTH) {
            return null;
        }

        const varInfo = new VarInfo(initializer, scope);
        const reDefValue = NumberUtils.getNumberByScope(arkFile, varInfo, rightOp);

        if (reDefValue.type === 0) {
            return reDefValue.value;
        }

        // 递归处理子作用域
        return this.processChildScopes(scope, initializer, arkFile, rightOp, depth);
    }

    private processChildScopes(scope: Scope, initializer: ArkAssignStmt, arkFile: ArkFile, rightOp: Value, depth: number = 0): number | null {
        if (depth > MAX_RECURSION_DEPTH || scope.childScopeList.length === 0) {
            return null;
        }

        for (const childScope of scope.childScopeList) {
            const result = this.processScope(childScope, initializer, arkFile, rightOp, depth + 1);
            if (result !== null) {
                return result;
            }
        }
        return null;
    }

    public reportIssue(arkFile: ArkFile, stmt: Stmt, methodName: string): void {
        const text = stmt.getOriginalText();
        if (!this.isValidText(text)) {
            return;
        }

        const issueInfo = this.buildIssueInfo(arkFile, stmt, methodName, text!);
        const defects = this.createDefects(issueInfo);
        this.issues.push(new IssueReport(defects, undefined));
    }

    private isValidText(text: string | undefined): text is string {
        return text !== undefined && text.length > 0;
    }

    private buildIssueInfo(arkFile: ArkFile, stmt: Stmt, methodName: string, text: string): IssueInfo {
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = arkFile.getFilePath();
        const originPositionInfo = stmt.getOriginPositionInfo();
        const lineNum = originPositionInfo.getLineNo();
        const methodIndex = text.lastIndexOf(methodName);
        const startColumn = originPositionInfo.getColNo() + (methodIndex >= 0 ? methodIndex : 0);
        const endColumn = startColumn + methodName.length;

        return {
            severity,
            filePath,
            lineNum,
            startColumn,
            endColumn
        };
    }

    private createDefects(issueInfo: IssueInfo): Defects {
        return new Defects(
            issueInfo.lineNum,
            issueInfo.startColumn,
            issueInfo.endColumn,
            this.metaData.description,
            issueInfo.severity,
            this.rule.ruleId,
            issueInfo.filePath,
            this.metaData.ruleDocPath,
            true,
            false,
            false
        );
    }

    private isEtsFile(filePath: string): boolean {
        return filePath.toLowerCase().endsWith('.ets');
    }

}